package com.eyedsion.his.web.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPObject;
import com.eyedsion.his.web.dao.dec.JpaBaseRepository;
import com.eyedsion.his.web.dao.dec.syncdata.DecSyncLogRepository;
import com.eyedsion.his.web.dao.his.MyBatisBaseMapper;
import com.eyedsion.his.web.entity.common.*;
import com.eyedsion.his.web.service.sycndata.WebServiceClient;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Table;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDate;
import java.util.*;

/**
 *
 *@author： 彭长云
 * @Description:用于同步数据，全量，增量更新数据
 */
public class JpaBaseService<T, ID extends Serializable> {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private WebServiceClient wsclient;
    @Autowired
    private EntityManagerFactory entityManagerFactory;
    @Autowired
    private EntityManager entityManager;
    @Autowired
    private JpaRepository<T, Serializable> baseRepository;
    @Autowired
    private MyBatisBaseMapper<T> baseMapper;
    @Autowired
    private DecSyncLogRepository decSyncLogRepository;
    //获取同步数据方式
    @Value("${syncdata.type}")
    private String syncType;
    //是否初始化数据
    @Value("${syncdata.init}")
    private boolean isInit;
    //通过数据库分页查询时，每次查询的数据条数
    @Value("${syncdata.pagehelper.pageSize}")
    private Integer pageSize;
    //默认批量提交大小
    @Value("${syncdata.default.batchSize}")
    private Integer defaultBatchSize;

    private static Map<String,SyncResult> syncMsgMap = new HashMap<>();

    /**
     * 设置同步信息
     */
    public static void setSyncMsgMap(String errorMsg,String status){
        StackTraceElement stack[] = (new Throwable()).getStackTrace();
        String callerClassName = "";
        SyncResult result;
        for(int i = 0;i< stack.length;i++){
            String className = stack[i].getClassName();
            if(className.contains("BaseDataSercie") || className.contains("BusinessDataService")){
                callerClassName = className;
                break;
            }
        }
        if(syncMsgMap.containsKey(callerClassName)){
            result = syncMsgMap.get(callerClassName);
            if(!DecSyncLog.SYNC_STATUS_ERROR.equals(result.getStatus())){
                result.setStatus(DecSyncLog.SYNC_STATUS_ERROR);
            }
        }else{
            result = new SyncResult();
            if(status != null){
                result.setStatus(status);
            }else{
                result.setStatus(DecSyncLog.SYNC_STATUS_ERROR);
            }
        }
        syncMsgMap.put(callerClassName,result);
        result.getErrorMsg().add(errorMsg);
    }

    public static Map<String,SyncResult> getSyncMsgMap(){
        return syncMsgMap;
    }

    /**
     * @Description:全量更新
     */
    public void fullUpdate() {
        fullUpdate(false);
    }

    /**
     * @Description:全量更新,isPaging：true分页同步
     */
    public void fullUpdate(boolean isPaging){
        try {
            //查询his库中的数据
            logger.info("开始全量更新数据,表名:{}",getClazz().getAnnotation(Table.class).name());

            //通过查询视图更新
            if("db".equals(syncType)){
                if(isPaging || isInit){
                    syncDataByPage(1,null, true,true);
                }else{
                    syncDataByDB(null, true,true);
                }
            }else{
                //调用webservice进行查询更新
                syncDataByWebService();
            }

            logger.info("同步完成");
            logger.info("————————————————————");
        } catch (Exception e) {
            setSyncMsgMap("同步" + getClazz().getAnnotation(Table.class).name() + "出错",null);
            logger.error("同步出现异常,{}",getClazz(),e);
        }
    }


    /**
     * @Description:增量更新
     */
    public void incrementUpdate(){
        incrementUpdate(false);
    }

    /**
     * @Description:增量更新
     */
    public void incrementUpdate(boolean isPaging){
        try {
            //如果选择webservice接口方式获取数据
            if("ws".equals(syncType)){
                syncDataByWebService();
            }else{
                //如果配置为初始化，则删除历史数据，进行分页同步
                if(isInit){
                    logger.info("初始化数据。。。,表名:{}",getClazz().getAnnotation(Table.class).name());
                    syncDataByPage(1,null,true,true);
                }else{
                    logger.info("开始增量更新数据,表名:{}",getClazz().getAnnotation(Table.class).name());

                    if(getCurrentUpdateCount() > 0){
                        logger.info("已经存在当天的数据，跳过此次更新！");
                        logger.info("----------------------------------------");
                        return;
                    }

                    //对于新增的数据默认为true
                    boolean isAdd = true;
                    if(decSyncLogRepository.isYesterdayFullUpdate() > 0){
                        logger.info("昨日数据已进行全量同步,此次将执行新增或者修改");
                        isAdd = false;
                    }

                    //新增数据开启创建日期过滤
                    FindParam createParam = new FindParam();
                    createParam.setByCreateTime(true);

                    //修改数据开启修改日期过滤
                    FindParam updateParam = new FindParam();
                    updateParam.setByUpdateTime(true);

                    logger.info("同步新增数据>>>");
                    if(isPaging){
                        syncDataByPage(1,createParam,false,isAdd);
                        logger.info("同步修改数据>>>");
                        syncDataByPage(1,updateParam,false,false);
                    }else{
                        syncDataByDB(createParam,false,isAdd);
                        logger.info("同步修改数据>>>");
                        syncDataByDB(updateParam,false,false);
                    }
                }
            }
            logger.info("同步完成");
            logger.info("————————————————————");
        } catch (Exception e) {
            setSyncMsgMap("同步" + getClazz().getAnnotation(Table.class).name() + "出错",null);
            logger.error("同步出现异常," + getClazz(),e);
        }
    }

    /**
     * @Description:同步数据
     * isDelHis:true 删除历史数据，false 不删除
     */
    private void syncDataByDB(FindParam param, boolean isDelHistory, boolean isAdd) {
        List<T> list = baseMapper.findAll(param);

        if(list.size() == 0){
            logger.info("查询到数据条数为0，同步结束");
            return;
        }
        logger.info("his库中数据条数:{}",list.size());
        //如果为true则表示全量更新,会删除原来的数据
        if (isDelHistory) {
            //删除对应表中的所有数据
            deleteAll();
            logger.info("成功删除历史数据");
        }

        if (isAdd) {
            //批量插入
            beatchInsert(list);
        } else {
            //批量修改
            beatchUpdate(list);
        }
    }

    /**
     * @Description:分页同步数据
     */
    private void syncDataByPage(Integer page,FindParam param, boolean isDelHistory, boolean isAdd){
        Integer pages = 0;
        try {
            //分页查询his库中的数据
            PageHelper.startPage(page, pageSize);
            List<T> list = baseMapper.findAll(param);
            Page pagelist = (Page) list;
            pages = pagelist.getPages();

            if(list.size() == 0){
                logger.info("查询到数据条数为0，同步结束");
                return;
            }
            if (page == 1) {
                logger.info("his库中数据条数:{}，每次查询条数:{}, 总页数:{}",pagelist.getTotal(),pageSize,pages );
                if(isDelHistory){
                    //删除对应表中的所有数据
                    deleteAll();
                    logger.info("成功删除历史数据");
                }
            }
            logger.info("开始同步第{}页",page);

            if (isAdd) {
                //批量插入
                beatchInsert(list);
            } else {
                //批量修改
                beatchUpdate(list);
            }

            if(page < pages){
                syncDataByPage(++page,param,isDelHistory,isAdd);
            }
        } catch (Exception e) {
            setSyncMsgMap("同步" + getClazz().getAnnotation(Table.class).name(),null);
            logger.error(getClazz() +  "同步出错",e);
        }
    }

    /**
     *
     *@author： 彭长云
     * @Description:批量插入数据，只回滚异常批次的数据，会缩小批量提交的大小进行重试
     * @date： 2018/12/13 9:28
     * @param： [listEntity]
     * @return： void
     */
    private void beatchInsert(List<T> listEntity){
        beatchInsert(listEntity,null,null,true);
    }

    /**
     *
     *@author： 彭长云
     * @Description:批量修改
     * @date： 2018/12/13 16:47
     * @param： [listEntity]
     * @return： void
     */
    private void beatchUpdate(List<T> listEntity){
        beatchInsert(listEntity,null,null,false);
    }

    /**
     *
     *@author： 彭长云
     * @Description:批量插入数据，只回滚异常批次的数据，会缩小批量提交的大小进行重试
     * @date： 2018/12/10 15:08
     * listEntity:数据集合
     * batchSize：批量提交大小
     * count:当前重试次数
     * isAdd:true新增，false修改
     */
    private void beatchInsert(List<T> listEntity,Integer batchSize,Integer count,boolean isAdd) {
        if(CollectionUtils.isEmpty(listEntity))return;
        if(batchSize == null)batchSize = defaultBatchSize;
        List<T> errList = new ArrayList<>();
        int entityCount = listEntity.size();
        //批量提交最后一次位置
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        String tableName = getClazz().getAnnotation(Table.class).name();
        transaction.begin();
        try {
            for (int i = 0; i < entityCount; i++) {
                try {
                    T entity = listEntity.get(i);
                    if(isAdd){
                        //新增
                        entityManager.persist(entity);
                    }else{
                        //修改
                        entityManager.merge(entity);
                    }
                    if ((i > 0 || batchSize == 1) && i % batchSize == 0) {
                        entityManager.flush();
                        entityManager.clear();
                        transaction.commit();
                        transaction.begin();
                    }

                } catch (Exception e) {
                    setSyncMsgMap("同步表" + tableName + "出错!",null);
                    //记录错误位置
                    int startIndex = i;
                    if(batchSize > 1){
                        startIndex = i < batchSize ? 0 : i - batchSize;
                    }
                    errList.addAll(listEntity.subList(startIndex,startIndex + batchSize));
                    transaction.rollback();
                    transaction.begin();
                    logger.error("n同步数据出错:", e);
                }
            }
            transaction.commit();
        } catch (RuntimeException e) {
            if (transaction != null && transaction.isActive()) {
                transaction.rollback();
            }
            errList.addAll(listEntity.subList(entityCount  -  (entityCount % batchSize) ,entityCount));
            setSyncMsgMap("同步表" + tableName + "出错!",null);
            logger.error("w同步数据出错:", e);
        }finally {
            entityManager.clear();
            entityManager.close();
        }

        //处理重试
        int errSize = errList.size();
        if(errSize > 0){
            count = count == null ? 1 : ++count;
            if(batchSize == 1){
                List<String> errCodeList = new ArrayList<>();
                for (int i=0;i< errSize;i++){
                    try {
                        T t = errList.get(i);
                        errCodeList.add(t.getClass().getMethod("getCode").invoke(t).toString());
                    } catch (Exception e) {
                        logger.error("获取失败数据code异常",e);
                    }
                }
                logger.error("同步出现异常,重试结束，异常数据条数:{}-{}",errSize,getClazz());
                logger.error("错误数据的Code:{}" ,errCodeList);
            }else {
                batchSize = batchSize / 5;
                if((count != null && count > 3) || batchSize == 0 || errSize <= batchSize){
                    batchSize = 1;
                }
                logger.info("同步出现异常，错误数据条数:{},开始第{}次重试,batchSize：{}", errSize,count,  batchSize);
                beatchInsert(errList,batchSize,++count,isAdd);
            }
        }
    }

    /**
     *
     *@author： 彭长云
     * @Description:查询Dde库中的所有数据
     * @date： 2018/12/13 9:28
     * @param： []
     * @return： java.util.List<T>
     */
    private List<T> findAll() {
        return baseRepository.findAll();
    }

    /**
     *
     *@author： 彭长云
     * @Description:删除dec库对应历史数据，全部删除
     * @date： 2018/12/13 9:30
     * @param： []
     * @return： void
     */
    private void deleteAll() {
        baseRepository.deleteAllInBatch();
    }

    /**
     *
     *@author： 彭长云
     * @Description:获取当天更新数据条数
     * @date： 2018/12/18 10:17
     * @param： []
     * @return： java.lang.Integer
     */
    private Integer getCurrentUpdateCount(){
        String tableName = getClazz().getAnnotation(Table.class).name();
        String sql = String.format("select count(1) from %s t where to_char(t.DEC_CREATE_TIME,'yyyy-mm-dd') = to_char(sysdate,'yyyy-mm-dd')",tableName);
        Object cntObj = entityManager.createNativeQuery(sql).getSingleResult();
        return Integer.parseInt(cntObj.toString());
    }


    private Class<T> getClazz() {
        Class<T> clazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return clazz;
    }

    ///////////////////////////////////////////////////////////////webservice///////////////////////////////////////////////////////////////////////////

    /**
     *
     *@author： 彭长云
     * @Description:调用webservice同步数据
     * @date： 2019/1/8 14:19
     * @param： []
     * @return： void
     */
    public void syncDataByWebService(){
        String yesterday = LocalDate.now().plusDays(-1).toString();
        //同步新增
        syncDataByWebService(new WebServiceParam(WebServiceParam.getType.add,yesterday));
        //同步修改
        syncDataByWebService(new WebServiceParam(WebServiceParam.getType.update,yesterday));
    }

    public void syncDataByWebService(WebServiceParam param){
        WebServiceResult<T> result = findAllByWebService(param);
        if(param.getPageIndex() == null || param.getPageIndex() == 0){
            logger.info("调用webservice查询到数据{}条,共{}页",result.getTotalCount(),result.getTotalPage());
        }
        logger.info("同步第{}页",result.getPageIndex());
        //新增
        if(WebServiceParam.getType.add.toString().equals(param.getGetType())){
            //批量插入新增的
            beatchInsert(result.getList());
        }else{
            //批量修改
            beatchUpdate(result.getList());
        }

        //判断是否需要递归翻页
        if(result.getPageIndex() != null && result.getTotalPage() != null
                && result.getPageIndex() < result.getTotalPage()){
            param.setPageIndex(result.getPageIndex() + 1);
            syncDataByWebService(param);
        }else{
            String type = WebServiceParam.getType.add.toString().equals(param.getGetType()) ? "新增" : "修改";
            logger.info("{}数据同步完成",type);
        }
    }

    /**
     *@author： 彭长云
     * @Description:调用webservice返回昨天的数据
     * @date： 2019/1/8 9:56
     * @param： []
     * @return： java.util.List<T>
     */
    public WebServiceResult<T> findAllByWebService(WebServiceParam param){
        //组装调用的方法
        String methodName = "get" + getClazz().getAnnotation(Table.class).name().replace("_","").toLowerCase();
        //调用webservice获取数据
        String jsonStr = wsclient.client(methodName,JSON.toJSONString(param)).toString();
        JSONObject jsonObj = JSON.parseObject(jsonStr);

        if(jsonObj.isEmpty() || StringUtils.isEmpty(jsonStr)){
            return new WebServiceResult<>();
        }

        //将webservice返回的数据放入实体类中，方便取用
        WebServiceResult<T> webServiceResult = new WebServiceResult<>();
        webServiceResult.setList(JSON.parseArray(jsonObj.getString("list"), getClazz()));
        webServiceResult.setStatus(jsonObj.getString("status"));
        webServiceResult.setMsg(jsonObj.getString("msg"));
        webServiceResult.setTotalCount(jsonObj.getInteger("totalCount"));
        webServiceResult.setPageIndex(jsonObj.getInteger("pageIndex"));
        webServiceResult.setPageSize(jsonObj.getInteger("pageSize"));
        webServiceResult.setTotalPage(jsonObj.getInteger("totalPage"));
        return webServiceResult;
    }
}
